package com.hmall.search.service.impl;

import cn.hutool.core.util.ObjectUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.FunctionBoostMode;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import com.hmall.common.domain.PageDTO;
import com.hmall.search.domain.po.ItemDoc;
import com.hmall.search.domain.query.ItemPageQuery;
import com.hmall.search.service.ISearchService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


@Service
@Slf4j
@AllArgsConstructor
public class SearchServiceImpl implements ISearchService {

    private final ElasticsearchClient esClient;


    /**
     * 完善黑马商城搜索
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<ItemDoc> query(ItemPageQuery query) throws IOException {
        SearchResponse<ItemDoc> response = esClient.search(builder -> builder
                        .index("items")
                        .query(querybuilder -> querybuilder.functionScore(fnscorebuilder -> fnscorebuilder
                                .query(querybuilder2 -> querybuilder2.bool(boolBuilder -> {
                                    //如果参数key（搜索框中的值）非空，就添加为bool里的must查询
                                    if (ObjectUtil.isNotEmpty(query.getKey())) {
                                        //使用match检索name字段值
                                        boolBuilder.must(mustBuilder -> mustBuilder
                                                .match(mb -> mb.field("name").query(query.getKey())));
                                    }
                                    //如果参数category非空，就添加为bool里的filter查询
                                    if (ObjectUtil.isNotEmpty(query.getCategory())) {
                                        //使用term检索category字段值
                                        boolBuilder.filter(filterBuilder -> filterBuilder
                                                .term(tb -> tb.field("category").value(query.getCategory())));
                                    }
                                    //如果参数brand非空，就添加为bool里的filter查询
                                    if (ObjectUtil.isNotEmpty(query.getBrand())) {
                                        //使用term检索brand字段值
                                        boolBuilder.filter(filterBuilder -> filterBuilder
                                                .term(tb -> tb.field("brand").value(query.getBrand())));
                                    }
                                    //如果参数minPrice非空，就添加为bool里的filter查询
                                    if (ObjectUtil.isNotNull(query.getMinPrice())) {
                                        //使用range检索price字段值，只设置gte
                                        boolBuilder.filter(filterBuilder -> filterBuilder
                                                .range(rb -> rb.field("price").gte(JsonData.of(query.getMinPrice()))));
                                    }
                                    //如果参数maxPrice非空，就添加为bool里的filter查询
                                    if (ObjectUtil.isNotNull(query.getMaxPrice())) {
                                        //使用range检索price字段值，只设置lte
                                        boolBuilder.filter(filterBuilder -> filterBuilder
                                                .range(rb -> rb.field("price").lte(JsonData.of(query.getMaxPrice()))));
                                    }

                                    return boolBuilder;
                                }))
                                .functions(fnbuilder -> fnbuilder
                                        //筛选出 isAD为true的商品
                                        .filter(filderbuilder -> filderbuilder.term(termbuilder -> termbuilder.field("isAD").value(true)))
                                        .weight(100D))
                                .boostMode(FunctionBoostMode.Multiply)
                        ))

                        //偏好规则，如果为空，按照默认字段排序   如果不为空，按照前端传入的规则排序
                        .sort(sortBuilder -> sortBuilder.field(fieldsort -> {
                            if (ObjectUtil.isEmpty(query.getSortBy())) {
                                //如果query里的sortby是空：要按照默认字段（_score关联度）排序
                                fieldsort.field("_score").order(SortOrder.Desc);
                            } else {
                                //否则：按query里的sortby字段排序
                                fieldsort.field(query.getSortBy())
                                        .order(query.getIsAsc() ? SortOrder.Asc : SortOrder.Desc);
                            }
                            return fieldsort;
                        }))
                        //构造分页条件：from+size。from是(当前页码-1)*每页数量
                        .from((query.getPageNo() - 1) * query.getPageSize())
                        .size(query.getPageSize())
                        .highlight(builder1 -> builder1.fields("name", filder -> filder
                                .preTags("<em style='color:bule'>").postTags("</em>")))
                , ItemDoc.class);

        //封装结果
        PageDTO<ItemDoc> docPageDTO = new PageDTO<>();
        long total = response.hits().total().value();
        docPageDTO.setTotal(total);
        List<ItemDoc> list = new ArrayList<>();
        List<Hit<ItemDoc>> hits = response.hits().hits();
        for (Hit<ItemDoc> hit : hits) {
            //先取原始文档对象
            ItemDoc doc = hit.source();
            //再取高亮结果
            Map<String, List<String>> map = hit.highlight();
            if (ObjectUtil.isNotEmpty(map)) {
                List<String> namevalues = map.get("name");
                if (ObjectUtil.isNotEmpty(namevalues)) {
                    doc.setName(namevalues.get(0));
                }
            }
            list.add(doc);
        }
        docPageDTO.setList(list);
        return docPageDTO;
    }
}
